package design.donkey.auto.ddl.analysis;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDateTime;
import java.util.*;

import design.donkey.auto.ddl.model.ColumnMetaModel;
import design.donkey.auto.ddl.model.IndexMetaModel;
import design.donkey.auto.ddl.model.TableMetaModel;
import design.donkey.auto.ddl.util.AutoDdlCommonUtils;
import design.donkey.framework.common.orm.annotation.*;
import design.donkey.framework.common.util.CommonUtils;
import lombok.extern.slf4j.Slf4j;

/**
 * 主键解析
 *
 * @author zhangjiakung
 * @since 0.0.1
 */
@Slf4j
public class AnnotationAnalysis {

    /**
     * 解析实体
     * 
     * @param domainClazz domainClazz
     * @return 表格信息
     */
    public TableMetaModel analysis(Class<?> domainClazz) {
        if (!isDomain(domainClazz)) {
            return null;
        }
        return analysisMeta(domainClazz);
    }

    /**
     * 解析实体
     *
     * @param domainClazz domainClazz
     * @return 表格信息
     */
    public TableMetaModel analysisMeta(Class<?> domainClazz) {
        // 获取列映射关系
        Map<ModelField, List<ColumnMetaModel>> columnHub = getColumns(domainClazz);
        // 获取索引信息
        List<IndexMetaModel> indexes = getIndexes(domainClazz);
        List<IndexMetaModel> indexList = replaceFieldToColumn(indexes, columnHub);

        // 解析表格信息
        TableMetaModel metaModel = new TableMetaModel();
        // 基础信息
        metaModel.setCatalog(getTableCatalog(domainClazz));
        metaModel.setSchema(getTableSchema(domainClazz));
        metaModel.setName(getTableName(domainClazz));
        metaModel.setComment(getTableComment(domainClazz));
        // 列信息
        metaModel.setColumns(getColumns(columnHub));
        // 索引信息
        metaModel.setIndexes(indexList);
        // 索引信息中的字段名称转换了列名称
        return metaModel;
    }

    /**
     * 是否为 domain
     * 
     * @param domainClazz domainClazz
     * @return 是否为 domain
     */
    public boolean isDomain(Class<?> domainClazz) {
        TableMeta annotation = domainClazz.getAnnotation(TableMeta.class);
        return annotation != null && !annotation.ignore();
    }

    /**
     * 列信息
     * 
     * @param columnHub columnHub
     * @return 列信息
     */
    protected List<ColumnMetaModel> getColumns(Map<ModelField, List<ColumnMetaModel>> columnHub) {
        List<ColumnMetaModel> resultList = new ArrayList<>();
        for (Map.Entry<ModelField, List<ColumnMetaModel>> modelFieldListEntry : columnHub.entrySet()) {
            resultList.addAll(modelFieldListEntry.getValue());
        }
        return resultList;
    }

    /**
     * 列信息
     * 
     * @param domainClazz domainClazz
     * @return 列信息
     */
    protected Map<ModelField, List<ColumnMetaModel>> getColumns(Class<?> domainClazz) {
        List<ModelField> modelFieldList = getDeclaredFieldsAndSupper(domainClazz);
        modelFieldList.sort((o1, o2) -> {
            // 比较用户自定义的顺序
            ColumnMeta annotation1 = o1.getAnnotation(ColumnMeta.class);
            ColumnMeta annotation2 = o2.getAnnotation(ColumnMeta.class);
            int customColumnLocationCompareToValue = Integer.compare(annotation1 == null ? 0 : annotation1.location(),
                annotation2 == null ? 0 : annotation2.location());
            if (customColumnLocationCompareToValue != 0) {
                return customColumnLocationCompareToValue;
            }
            // 如果用户未自定义顺序，或自定义顺序相同则根据 解析顺序排序
            int analysisColumnLocationCompareToValue =
                Integer.compare(o1.getAnalysisColumnLocation(), o2.getAnalysisColumnLocation());
            if (analysisColumnLocationCompareToValue != 0) {
                return analysisColumnLocationCompareToValue;
            }
            // 如果解析列顺序仍然相同，则使用字段名称排序
            if (o1.getName() != null && o2.getName() != null) {
                return o1.getName().compareTo(o2.getName());
            }
            return o1.getName() == null ? -1 : 1;
        });

        Map<ModelField, List<ColumnMetaModel>> columnMetaModeHub = new LinkedHashMap<>();
        for (ModelField modelField : modelFieldList) {
            List<ColumnMetaModel> columns = getColumns(modelField);
            columnMetaModeHub.put(modelField, columns);
        }
        return columnMetaModeHub;
    }

    /**
     * 获取列
     * 
     * @param field field
     * @return field
     */
    protected List<ColumnMetaModel> getColumns(ModelField field) {
        ColumnMeta columnMetaAnnotation = field.getAnnotation(ColumnMeta.class);
        if (columnMetaAnnotation != null && columnMetaAnnotation.ignore()) {
            return Collections.emptyList();
        }

        ColumnMetaModel columnMetaModel = new ColumnMetaModel();
        columnMetaModel.setName(getColumnName(field));
        columnMetaModel.setJdbcType(getColumnJdbcType(field));
        columnMetaModel.setLength(getColumnLength(field));
        columnMetaModel.setPrecision(getColumnPrecision(field));
        columnMetaModel.setScale(getColumnScale(field));
        columnMetaModel.setZerofill(getColumnZerofill(field));
        columnMetaModel.setUnsigned(getColumnUnsigned(field));
        columnMetaModel.setNullable(getColumnNullable(field));
        columnMetaModel.setDefaultValue(getColumnDefaultValue(field));
        columnMetaModel.setDefaultValueForUpdate(getColumnDefaultValueForUpdate(field));
        columnMetaModel.setComment(getColumnComment(field));
        columnMetaModel.setColumnDefinition(getColumnDefinition(field));
        columnMetaModel.setPrimaryKey(getColumnPrimaryKey(field));
        columnMetaModel.setPrimaryKeyType(getColumnPrimaryKeyType(field));
        columnMetaModel.setUnique(getColumnUnique(field));
        return Collections.singletonList(columnMetaModel);
    }

    /**
     * 获取列名称
     * 
     * @param field 列名称
     * @return 列名称
     */
    protected String getColumnName(ModelField field) {
        ColumnMeta columnMetaAnnotation = field.getAnnotation(ColumnMeta.class);
        return get(columnMetaAnnotation == null ? null : columnMetaAnnotation.value(),
            columnMetaAnnotation == null ? null : columnMetaAnnotation.name())
                .orElseGet(() -> CommonUtils.toUnderlineCase(field.getName()));
    }

    /**
     * 获取列类型
     *
     * @param field 列
     * @return 类型
     */
    protected JdbcType getColumnJdbcType(ModelField field) {
        // 用户自定义了 字段类型
        ColumnMeta columnMetaAnnotation = field.getAnnotation(ColumnMeta.class);
        if (columnMetaAnnotation != null && !JdbcType.AUTO.equals(columnMetaAnnotation.jdbcType())) {
            return columnMetaAnnotation.jdbcType();
        }
        return deductionFieldJdbcType(field.getFiledType());
    }

    /**
     * 获取列 长度
     *
     * @param field 列名称
     * @return value
     */
    protected int getColumnLength(ModelField field) {
        // 用户自定义了 字段类型
        ColumnMeta columnMetaAnnotation = field.getAnnotation(ColumnMeta.class);
        if (columnMetaAnnotation != null) {
            return columnMetaAnnotation.length();
        }
        return 255;
    }

    /**
     * 获取列 数值长度
     *
     * @param field 列名称
     * @return value
     */
    protected int getColumnPrecision(ModelField field) {
        // 用户自定义了 字段类型
        ColumnMeta columnMetaAnnotation = field.getAnnotation(ColumnMeta.class);
        if (columnMetaAnnotation != null) {
            return columnMetaAnnotation.precision();
        }
        return 0;
    }

    /**
     * 获取列 小数精度
     *
     * @param field 列名称
     * @return value
     */
    protected int getColumnScale(ModelField field) {
        // 用户自定义了 字段类型
        ColumnMeta columnMetaAnnotation = field.getAnnotation(ColumnMeta.class);
        if (columnMetaAnnotation != null) {
            return columnMetaAnnotation.scale();
        }
        return 0;
    }

    /**
     * 获取列 Zerofill
     *
     * @param field 列名称
     * @return value
     */
    protected boolean getColumnZerofill(ModelField field) {
        // 用户自定义了 字段类型
        ColumnMeta columnMetaAnnotation = field.getAnnotation(ColumnMeta.class);
        if (columnMetaAnnotation != null) {
            return columnMetaAnnotation.zerofill();
        }
        return false;
    }

    /**
     * 获取列 unsigned
     *
     * @param field 列名称
     * @return value
     */
    protected boolean getColumnUnsigned(ModelField field) {
        // 用户自定义了 字段类型
        ColumnMeta columnMetaAnnotation = field.getAnnotation(ColumnMeta.class);
        if (columnMetaAnnotation != null) {
            return columnMetaAnnotation.unsigned();
        }
        return false;
    }

    /**
     * 获取列 nullable
     *
     * @param field 列名称
     * @return value
     */
    protected boolean getColumnNullable(ModelField field) {
        // 用户自定义了 字段类型
        ColumnMeta columnMetaAnnotation = field.getAnnotation(ColumnMeta.class);
        if (columnMetaAnnotation != null) {
            return columnMetaAnnotation.nullable();
        }
        return true;
    }

    /**
     * 获取列 defaultValue
     *
     * @param field 列名称
     * @return value
     */
    protected String getColumnDefaultValue(ModelField field) {
        // 用户自定义了 字段类型
        ColumnMeta columnMetaAnnotation = field.getAnnotation(ColumnMeta.class);
        if (columnMetaAnnotation != null) {
            return columnMetaAnnotation.defaultValue();
        }
        return null;
    }

    /**
     * 获取列 defaultValueForUpdate
     *
     * @param field 列名称
     * @return value
     */
    protected String getColumnDefaultValueForUpdate(ModelField field) {
        // 用户自定义了 字段类型
        ColumnMeta columnMetaAnnotation = field.getAnnotation(ColumnMeta.class);
        if (columnMetaAnnotation != null) {
            return columnMetaAnnotation.defaultValueForUpdate();
        }
        return null;
    }

    /**
     * 获取列 comment
     *
     * @param field 列名称
     * @return value
     */
    protected String getColumnComment(ModelField field) {
        // 用户自定义了 字段类型
        ColumnMeta columnMetaAnnotation = field.getAnnotation(ColumnMeta.class);
        if (columnMetaAnnotation != null) {
            return columnMetaAnnotation.comment();
        }
        return null;
    }

    /**
     * 获取列 comment
     *
     * @param field 列名称
     * @return value
     */
    protected String getColumnDefinition(ModelField field) {
        // 用户自定义了 字段类型
        ColumnMeta columnMetaAnnotation = field.getAnnotation(ColumnMeta.class);
        if (columnMetaAnnotation != null) {
            return columnMetaAnnotation.columnDefinition();
        }
        return null;
    }

    /**
     * 获取列 PrimaryKey
     *
     * @param field 列名称
     * @return value
     */
    protected boolean getColumnPrimaryKey(ModelField field) {
        // 用户自定义了 字段类型
        ColumnMeta columnMetaAnnotation = field.getAnnotation(ColumnMeta.class);
        if (columnMetaAnnotation != null) {
            return columnMetaAnnotation.primaryKey();
        }
        return false;
    }

    /**
     * 获取列 PrimaryKey
     *
     * @param field 列名称
     * @return value
     */
    protected PrimaryKeyType getColumnPrimaryKeyType(ModelField field) {
        // 用户自定义了 字段类型
        ColumnMeta columnMetaAnnotation = field.getAnnotation(ColumnMeta.class);
        if (columnMetaAnnotation != null) {
            return columnMetaAnnotation.primaryKeyType();
        }
        return null;
    }

    /**
     * 获取列 unique
     *
     * @param field 列名称
     * @return value
     */
    protected boolean getColumnUnique(ModelField field) {
        // 用户自定义了 字段类型
        ColumnMeta columnMetaAnnotation = field.getAnnotation(ColumnMeta.class);
        if (columnMetaAnnotation != null) {
            return columnMetaAnnotation.unique();
        }
        return false;
    }

    /**
     * 推断 jdbc type
     *
     * @param filedType 字段类型
     * @return jdbc type
     */
    protected JdbcType deductionFieldJdbcType(Class<?> filedType) {
        if (Number.class.isAssignableFrom(filedType)) {
            if (Double.class.isAssignableFrom(filedType) || Float.class.isAssignableFrom(filedType)
                || BigDecimal.class.isAssignableFrom(filedType)) {
                return JdbcType.DECIMAL;
            } else if (Long.class.isAssignableFrom(filedType) || BigInteger.class.isAssignableFrom(filedType)) {
                return JdbcType.BIGINT;
            } else {
                return JdbcType.INT;
            }
        } else if (Date.class.isAssignableFrom(filedType)
            || filedType.getName().startsWith(LocalDateTime.class.getPackage().getName())) {
            return JdbcType.DATETIME;
        } else if (Boolean.class.isAssignableFrom(filedType)) {
            return JdbcType.TINYINT;
        } else {
            return JdbcType.VARCHAR;
        }
    }

    /**
     * 获取索引信息
     * 
     * @param domainClazz domainClazz
     * @return 索引信息
     */
    protected List<IndexMetaModel> getIndexes(Class<?> domainClazz) {
        TableMeta tableMetaAnnotation = domainClazz.getAnnotation(TableMeta.class);
        if (tableMetaAnnotation == null || CommonUtils.isEmpty(tableMetaAnnotation.indexes())) {
            return Collections.emptyList();
        }
        List<IndexMetaModel> resultList = new ArrayList<>();
        IndexMeta[] indexes = tableMetaAnnotation.indexes();
        for (IndexMeta index : indexes) {
            IndexMetaModel indexMetaModel = new IndexMetaModel();
            indexMetaModel.setName(index.name());
            indexMetaModel.setColumnNames(index.columnNames());
            indexMetaModel.setUnique(index.unique());
            indexMetaModel.setPrimaryKey(index.primaryKey());
            resultList.add(indexMetaModel);
        }
        return resultList;
    }

    /**
     * 替换字段名称为列名称
     *
     * @param indexes indexes
     * @param columnHub columnHub
     * @return 索引信息
     */
    protected List<IndexMetaModel> replaceFieldToColumn(List<IndexMetaModel> indexes,
        Map<ModelField, List<ColumnMetaModel>> columnHub) {

        Map<String, List<ColumnMetaModel>> columnHubForName = new HashMap<>(columnHub.size());
        for (Map.Entry<ModelField, List<ColumnMetaModel>> modelFieldListEntry : columnHub.entrySet()) {
            ModelField key = modelFieldListEntry.getKey();
            columnHubForName.put(key.getName(), modelFieldListEntry.getValue());
        }

        for (IndexMetaModel index : indexes) {
            if (CommonUtils.isEmpty(index.getColumnNames())) {
                continue;
            }
            List<String> newColumns = new ArrayList<>();
            for (String columnName : index.getColumnNames()) {
                List<ColumnMetaModel> columnMetaModelList = columnHubForName.get(columnName);
                if (CommonUtils.isNotEmpty(columnMetaModelList)) {
                    for (ColumnMetaModel columnMetaModel : columnMetaModelList) {
                        newColumns.add(columnMetaModel.getName());
                    }
                } else {
                    newColumns.add(columnName);
                }
            }
            index.setColumnNames(newColumns.toArray(new String[0]));
        }
        return indexes;
    }

    /**
     * 获取表格 Catalog
     * 
     * @param domainClazz domainClazz
     * @return Catalog
     */
    protected String getTableCatalog(Class<?> domainClazz) {
        TableMeta tableMetaAnnotation = domainClazz.getAnnotation(TableMeta.class);
        return get(tableMetaAnnotation == null ? null : tableMetaAnnotation.catalog()).orElse(null);
    }

    /**
     * 获取表格 schema
     * 
     * @param domainClazz domainClazz
     * @return schema
     */
    protected String getTableSchema(Class<?> domainClazz) {
        TableMeta tableMetaAnnotation = domainClazz.getAnnotation(TableMeta.class);
        return get(tableMetaAnnotation == null ? null : tableMetaAnnotation.schema()).orElse(null);
    }

    /**
     * 获取表格 schema
     * 
     * @param domainClazz domainClazz
     * @return schema
     */
    protected String getTableName(Class<?> domainClazz) {
        TableMeta tableMetaAnnotation = domainClazz.getAnnotation(TableMeta.class);
        return get(tableMetaAnnotation == null ? null : tableMetaAnnotation.value(),
            tableMetaAnnotation == null ? null : tableMetaAnnotation.name())
                .orElseGet(() -> CommonUtils.toUnderlineCase(domainClazz.getSimpleName()));
    }

    /**
     * 获取表格 comment
     * 
     * @param domainClazz domainClazz
     * @return comment
     */
    protected String getTableComment(Class<?> domainClazz) {
        TableMeta tableMetaAnnotation = domainClazz.getAnnotation(TableMeta.class);
        return get(tableMetaAnnotation == null ? null : tableMetaAnnotation.comment()).orElse(null);
    }

    /**
     * 获取实体的所有字段，包括父类的字段名称
     *
     * @param entityType 实体类型
     * @return 实体的所有字段
     */
    protected List<ModelField> getDeclaredFieldsAndSupper(Class<?> entityType) {
        if (Object.class.equals(entityType)) {
            // 使用 LinkedHashMap 保证解析顺序
            return new LinkedList<>();
        }
        // 解析父类的所有字段
        List<ModelField> modelFieldList = getDeclaredFieldsAndSupper(entityType.getSuperclass());

        // 解析当前类的所有字段
        Field[] currentFieldArray = entityType.getDeclaredFields();
        for (Field field : currentFieldArray) {
            String fieldName = field.getName();
            Method getter = AutoDdlCommonUtils.getter(entityType, fieldName, field.getType());
            Method setter = AutoDdlCommonUtils.setter(entityType, fieldName, field.getType());
            if (getter != null && setter != null) {
                // 设置列信息
                ModelField modelField = new ModelField();
                modelField.setName(fieldName);
                modelField.setField(field);
                modelField.setGetter(getter);
                modelField.setSetter(setter);
                modelField.setAnalysisColumnLocation(modelFieldList.size());
                modelFieldList.add(modelField);
            } else {
                if (log.isDebugEnabled()) {
                    log.debug("{} Not Fund {} Getter Or Setter!", entityType, fieldName);
                }
            }
        }

        return modelFieldList;
    }

    /**
     * 获取第一个非空字符
     * 
     * @param valueArray 字符列表
     * @return 非空字符
     */
    protected Optional<String> get(String... valueArray) {
        if (CommonUtils.isEmpty(valueArray)) {
            return Optional.empty();
        }

        for (String value : valueArray) {
            if (CommonUtils.isNotBlank(value)) {
                return Optional.of(value.trim());
            }
        }
        return Optional.empty();
    }

}
